/**
 * @file led.c
 * @author itxiaoye
 * @brief 
 * @version 0.1
 * @date 2022-05-17
 * 点灯操作，驱动程序
 * @copyright Copyright (c) 2022
 * 
 */

/*******************************************************************************
* Include
*******************************************************************************/
//  module
#include <linux/module.h>
#include <linux/init.h>
// platform
#include <linux/platform_device.h>
//API for device tree
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
//API for thread 
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/mutex.h>
//字符设备
#include <linux/fs.h>
#include <linux/cdev.h>
//检查
#include <linux/uaccess.h>

#include "led.h"

/*******************************************************************************
* Macro
*******************************************************************************/
#define IT_XIAOYE_LED_TWINKLE 0 // led灯闪烁功能是否打开

/*******************************************************************************
* Typedefs
*******************************************************************************/
// 字符设备数据
typedef struct ItxiaoyeLedDev{
	int led_id; // 设备id
	struct class* led_class; // 设备类
}ItxiaoyeLedDev;

// 设备数据结构
typedef struct ItxiaoyeLedData{
	int count;  // gpio个数
	int gpio[0]; // 可变长度数组
	struct mutex mtx; // 互斥锁
	struct task_struct *ledThread; // 灯线程任务句柄
	ItxiaoyeLedDev itxiaoyeLedDev; // led灯设备
}ItxiaoyeLedData;


/*******************************************************************************
* Local functions declaration
*******************************************************************************/

/*******************************************************************************
* Global variables
*******************************************************************************/
/* 常量数据 */
static const char *DEV_NAME = "itxiaoye_led"; // 设备名称
static const char *DEV_CLASS_NAME = "itxiaoye_led_class"; // 设备类名称

static ItxiaoyeLedData *publicData; // 公共数据

/*******************************************************************************
* Global functions
*******************************************************************************/

#if IT_XIAOYE_LED_TWINKLE
/**
 * @author: itxiaoye
 * @description: 灯线程任务
 * @param: 模块私有数据
 * @return: 无
 */
static int led_thread_func(void *data) {	
	int i, count;
	ItxiaoyeLedData *publicData = (ItxiaoyeLedData *)data;

	/* 循环设置gpio高低电平 */
	while (1){
		count++;
		mutex_lock(&publicData->mtx);
		for ( i = 0; i < publicData->count; i++){
			gpio_set_value(publicData->gpio[i], count%2);
		}
		mutex_unlock(&publicData->mtx);
		msleep(100);
		printk(KERN_INFO "thread count %d\n", count);
	}
	return 0;
}
#endif // IT_XIAOYE_LED_TWINKLE

static int itxiaoye_led_open (struct inode *inode, struct file *file){
	file->private_data = publicData;
	return 0;
}

static ssize_t itxiaoye_led_read (struct file *file, char __user *buff, size_t size, loff_t *ppos){
	return 0;
}

static ssize_t itxiaoye_led_write (struct file *file, const char __user *buff, size_t size, loff_t *ppos){
	return 0;
}

static long itxiaoye_led_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
	int err = 0;
	int data = 0;
	int ret = 0;
	int i;

	/* 验证命令cmd参数的有效性 */
	if(_IOC_TYPE(cmd) != ITXIAOYE_LED_IOC_MAGIC){
		return -EINVAL;
	}
	if(_IOC_NR(cmd) > itxiaoyeLedIocMax){
		return -EINVAL;
	}

	/* 检测参数空间是否可以正确访问 */
	if(_IOC_DIR(cmd) & _IOC_READ){
		err = !access_ok((void *)arg,_IOC_SIZE(cmd));
	}
	if(err){
		return -EFAULT;
	}

	/* 解析数据 */
	switch(cmd){
		case ITXIAOYE_LED_IOC_LED_CONTR:
			ret = __get_user(data, (int *)arg);
			for (i = 0; i < publicData->count; i++){
				gpio_set_value(publicData->gpio[i], (data + 1)%2);
			}
			break;
		default:
			return -EFAULT;
	}

	return 0;
}

static const struct file_operations itxiaoye_led_fops = {
	.owner = THIS_MODULE,
	.open = itxiaoye_led_open,
	.read = itxiaoye_led_read,
	.write = itxiaoye_led_write,
	.unlocked_ioctl = itxiaoye_led_ioctl,
};

static int itxiaoye_led_probe(struct platform_device *pdev){
    int ret,i;
    struct device *dev = &pdev->dev;
    struct device_node *node = dev->of_node;

    if(!node)
        return -EINVAL;

    ret = of_gpio_count(node); // 获取gpio的个数
    if(ret == 0){
        return -EINVAL;
    }
    
    publicData = kzalloc(sizeof(ItxiaoyeLedData), GFP_KERNEL); // 开辟空间存储数据
	
	if (!publicData){
		return -ENOMEM;
	}
	
	/* 保存gpio数据信息 */
	publicData->count = ret;
	mutex_init(&publicData->mtx);
	for (i = 0; i < publicData->count; i++) {
		unsigned int gpio;
		gpio = of_get_gpio(node, i);
		if (gpio < 0) {
			dev_warn(dev, "Unable to get gpio #%d\n", i);
			continue;		
		}
		ret = devm_gpio_request_one(dev, gpio, GPIOF_DIR_OUT, pdev->name);
		publicData->gpio[i] = gpio;
		if (ret < 0) {
			dev_warn(dev, "Unable to re quest GPIO %d: %d\n",
                      gpio, ret);
			continue;
		}
		printk(KERN_INFO "success request gpio %d\n",gpio);
		
		gpio_direction_output(gpio, 1); //设置输出的电平
	}

	/* 注册设备 */
	publicData->itxiaoyeLedDev.led_id = register_chrdev(0, 				// 主设备号,为0则系统自动分配
												 DEV_NAME,			// 设备名称
												 &itxiaoye_led_fops // 文件操作
												 );

	publicData->itxiaoyeLedDev.led_class = class_create(THIS_MODULE, DEV_CLASS_NAME); // 创建一个类
	ret = PTR_ERR(publicData->itxiaoyeLedDev.led_class);
  	if (IS_ERR(publicData->itxiaoyeLedDev.led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(publicData->itxiaoyeLedDev.led_id, DEV_NAME);
		return -1;
	}

	/* 创建设备，该设备创建在类下面 */
  	device_create(publicData->itxiaoyeLedDev.led_class, NULL, MKDEV(publicData->itxiaoyeLedDev.led_id, 0), NULL, DEV_NAME); // /dev/dev_name 


	#if IT_XIAOYE_LED_TWINKLE
		/* 创建运行新线程 */
		publicData->ledThread = kthread_create(led_thread_func, publicData, "thread_pwm");
		if(publicData->ledThread)
		{
			wake_up_process(publicData->ledThread);
		}
	#endif // IT_XIAOYE_LED_TWINKLE

	return 0;
}

static int itxiaoye_led_remove(struct platform_device *pdev){

	unregister_chrdev(publicData->itxiaoyeLedDev.led_id, DEV_NAME); // 注销设备
	class_destroy(publicData->itxiaoyeLedDev.led_class); // 注销设备类

	kfree(publicData); // 释放数据空间

	#if IT_XIAOYE_LED_TWINKLE
		/* 结束灯线程 */
		if(publicData->ledThread){
			kthread_stop(publicData->ledThread);
			publicData->ledThread = NULL;
		}
	#endif // IT_XIAOYE_LED_TWINKLE

	return 0;
}

static struct of_device_id itxiaoye_led_of_match[] = {
    {.compatible = "itxiaoye_led"}
};

static struct platform_driver itxiaoye_led_driver = {
    .probe = itxiaoye_led_probe,
	.remove = itxiaoye_led_remove,
    .driver = {
        .name = "itxiaoye-led-device",
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(itxiaoye_led_of_match)
    }
};

static int __init led_init(void){
    printk("welcome,itxiaoye led \n");
    return platform_driver_register(&itxiaoye_led_driver);
}

static void led_exit(void){
    printk("bye,itxiaoye led \n");
    platform_driver_unregister(&itxiaoye_led_driver);
}


module_init(led_init);
module_exit(led_exit);

// 版权声明
MODULE_LICENSE("GPL v2");